﻿using System.Collections.ObjectModel;
using RLProject1.Model;
using System.Collections.Generic;
using System.Windows;
using System.Diagnostics;
using System;
using System.Windows.Input;
using MvvmFoundation.Wpf; //RelayCommand; siehe http://msdn.microsoft.com/en-us/magazine/dd419663.aspx#id0090030

namespace RLProject1.ViewModel {

    /// <summary>
    /// ViewModel zur Darstellung der Daten im Hauptfenster.
    /// <para>
    /// Intern enthält dieses ViewModel eine Collection von ViewModels, die über das entsprechende
    /// Property im XAML eingebunden werden können, bspw. durch:
    /// <code>
    /// &lt;ItemsControl ItemsSource="{Binding ViewModels}" /&gt;
    /// </code>
    /// </para><para>
    /// Der Konstruktor erzeugt die Daten (Model.GridStateQValues u.a.), übergibt sie
    /// dem ViewModel.GridViewModel und fügt sie der internen ViewModels-Collection hinzu.
    /// Im XAML wird per DataTemplate das GridViewModel zur Darstellung auf das UserControl
    /// 'GridView' abgebildet.
    /// </para>
    /// </summary>
    class MainWindowViewModel : ViewModelBase {

        GridWorld _world;
        SarsaForDummies _sarsa;
        GridAction action;
        GridViewModel gvm;
        RelayCommand _doStepCommand;
        RelayCommand _singleStepCommand;
        RelayCommand _toStartCommand;
        Random _random;
        GridState[] _finals = { 44 }; //TODO: Endzustand hier noch hart gecodet!!

        /// <summary>
        /// Sammlung einzerlner view-models, die im MainWindow zusammen dargestellt werden (können)
        /// </summary>
        ObservableCollection<ViewModelBase> _viewModels;

        /// <summary>
        /// Erzeugt neue ViewModel zur Darstellung der Daten im Hauptfenster.
        /// </summary>
        public MainWindowViewModel() {
            initWorld();
            _random = new Random();

            //create instance of our view model to display data, add it to our collection
            int numRows = 10;
            gvm = new GridViewModel(_sarsa, numRows);
            ViewModels.Add(gvm);
        }

        #region Properties
        public double Gamma {
            get { return _sarsa.DiscountFactor; }
            set {
                _sarsa.DiscountFactor = value;
                OnPropertyChanged("Gamma");
            }
        }
        public double Epsilon {
            get { return _sarsa.ExplorationFactor; }
            set {
                _sarsa.ExplorationFactor = value;
                OnPropertyChanged("Epsilon");
            }
        }
        public double Alpha {
            get { return _sarsa.LearnRate; }
            set {
                _sarsa.LearnRate = value;
                OnPropertyChanged("Alpha");
            }
        }
        public double Lambda {
            get { return _sarsa.TraceDecay; }
            set {
                _sarsa.TraceDecay = value;
                OnPropertyChanged("TraceDecayFactor");
            }
        }

        /// <summary>
        /// Gibt die ViewModels
        /// </summary>
        public ObservableCollection<ViewModelBase> ViewModels {
            get {
                if (_viewModels == null) {
                    _viewModels = new ObservableCollection<ViewModelBase>();
                }
                return _viewModels;
            }
        }

        int _row = 0;
        /// <summary>
        /// Gibt die Zeilenposition des Agenten.
        /// </summary>
        public int AgentRow {
            get { return _row; }
            private set {
                _row = value;
                OnPropertyChanged("AgentRow");
            }
        }

        int _col = 0;
        /// <summary>
        /// Gibt die Spaltenposition des Agenten.
        /// </summary>
        public int AgentCol {
            get { return _col; }
            private set {
                _col = value;
                OnPropertyChanged("AgentCol");
            }
        }
        #endregion

        private void initWorld()
        {
            int rows = 10;
            int columns = 10;
            _world = new GridWorld(rows, columns, _finals); //10*10 Grid mit Bewegungskosten
            _world[43, GridAction.EAST]         = 100;     // dicke Belohnung
            _world[54, GridAction.NORTH]        =  10;     // kleine Belohnung für Rest
            _world[53, GridAction.NORTHEAST]    =  10;
            _world[33, GridAction.SOUTHEAST]    =  10;
            _world[34, GridAction.SOUTH]        =  10;
            _world[35, GridAction.SOUTHWEST]    =  10;
            _world[45, GridAction.WEST]         =  10;
            _world[55, GridAction.NORTHWEST]    =  10;
            this.setStartState(false);
            setOccupied(true);
            _sarsa = new SarsaForDummies(_world, new EpsilonGreedyForGrid(0.1), 0.5, 0.8, 0.8);
            evalAgentPos();
            this.action = _sarsa.Policy.GetAction(_sarsa.QFunction[_world.State].All);
        }

        

        #region Widget Commands

        /// <summary>
        /// Gibt das ICommand zum Ausführen einer Schrittfolge.
        /// </summary>
        public ICommand DoStepCommand {
            get {
                if (_doStepCommand == null)
                    _doStepCommand = new RelayCommand( () => doStepExecute(), () => doStepCanExecute());
                return _doStepCommand;
            }
        }

        /// <summary>
        /// Gibt das ICommand zum Ausführen eines Schrittes.
        /// </summary>
        public ICommand DoSingleStepCommand {
            get {
                if (_singleStepCommand == null)
                    _singleStepCommand = new RelayCommand(() => doSingleStepExecute(), () => doSingleStepCanExecute());
                return _singleStepCommand;
            }
        }

        /// <summary>
        /// Gibt das ICommand, um den Agenten wieder auf Startposition zu setzen.
        /// </summary>
        public ICommand ToStartCommand {
            get {
                if (_toStartCommand == null)
                    _toStartCommand = new RelayCommand( () => toStartExecute(), () => toStartCanExecute());
                return _toStartCommand;
            }
        }

        

        private void doStepExecute() {
            setOccupied(false);
            //int count = _sarsa.RunEpisode(20, _finals);//_sarsa.runEpisode(20, _finals);
            int count = _sarsa.RunEpisode(20);
            action = _sarsa.Policy.GetAction(_sarsa.QFunction[_world.State].All);
            evalAgentPos();
        }

        private void doSingleStepExecute() {
            setOccupied(false);
            action = _sarsa.DoStep(action);
            evalAgentPos();
        }

        private void toStartExecute() {
            setOccupied(false);
            this.setStartState(false);
            _sarsa.Reset(); //resetEpisode()

            evalAgentPos();
            action = _sarsa.Policy.GetAction(_sarsa.QFunction[_world.State].All);
            // Pfadpfeile ausblenden:
            foreach (ViewModelBase model in this.ViewModels) {
                GridViewModel gvm = model as GridViewModel;
                if (gvm != null) {
                    gvm.hideArrows();
                    //gvm.setEligibilities(_sarsa.Eligibilities);
                }
            }
            // 
        }

        /// <summary>
        /// Setzt den Weltzustand, ggf. zufällig.
        /// </summary>
        /// <param name="random">true, wenn Zustand zufällig gewählt werden soll, false für Standardtwert (0)</param>
        void setStartState(bool random) {
            GridState start = random ? this._random.Next(this._world.StateCount) : 0;
            this._world.State = start;
        }

        private bool doStepCanExecute() {
            return _world.State != _finals[0];
        }
        private bool doSingleStepCanExecute() {
            return _world.State != _finals[0];
        }
        private bool toStartCanExecute() {
            //TODO: Startzustand hier noch hart gecodet!!
            return _world.State != 0;
        }

        #endregion

        /// <summary>
        /// Setzt die Properties AgentRow und AgentCol entsprechend der aktuellen Agentenposition und
        /// aktualisiert die Occupied-Eigenschaft des aktuellen Feldes.
        /// </summary>
        private void evalAgentPos() {
            AgentRow = _world.getRowIdx(_world.State);
            AgentCol = _world.getColIdx(_world.State);
            setOccupied(true);
        }

        /// <summary>
        /// Setzt den 'Besetzt'-Status der aktuellen Position auf den angegebenen Wert.
        /// </summary>
        /// <param name="isOccupied">True, wenn das aktuelle Feld (AgentRow und AgentCol) besetzt ist. False, wenn es frei ist bzw.
        /// verlassen wird.</param>
        private void setOccupied(bool isOccupied) {
            foreach (ViewModelBase model in this.ViewModels) {
                GridViewModel gvm = model as GridViewModel;
                if (gvm != null) {
                    gvm.setOccupied(AgentRow, AgentCol, isOccupied);
                }
            }
        }

    }
}
